﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using Un4seen.Bass.AddOn.Tags;
using mxplay.Properties;
using System.Threading;
using System.Windows.Forms;
using System.Reflection;

namespace mxplay
{
	class PlayListTag
	{
		/*
		 * Прибиндить ListView
		 * типа BindListView(ListView listview) ->
		 * Чтобы все управление списком было бы
		 * через методы списка без лополнительного привлечения ListView
		 */

		#region list view
		private ListView listview;
		public void BindListview(ListView control)
		{
			if (control == null) return;

			listview = control;

			//listview будет виртуальным - он быстрее работает
			listview.VirtualMode = true;
			
			UpdateColumns();
			
			listview.VirtualListSize = entries.Count;
			//            listview.CacheVirtualItems += new CacheVirtualItemsEventHandler(listview_CacheVirtualItems);
			listview.RetrieveVirtualItem += new RetrieveVirtualItemEventHandler(listview_RetrieveVirtualItem);
		}

		internal void UpdateColumns()
		{
			listview.BeginUpdate();
			//обовим столбцы
			//в свойстве Tag столбца будем хранить описание соответствующего поля из TAG_INFO
			Type taginfo_type=typeof(TAG_INFO);
			listview.Columns.Clear();
			foreach(int ind in PLaylistColumns.VisibleColumns)
			{
				ColumnHeader head=listview.Columns.Add(PLaylistColumns.AvailableColumnsH(ind));
				head.Tag=taginfo_type.GetField(PLaylistColumns.AvailableColumns(ind));
			}
			listview.EndUpdate();
		}
		
		void listview_RetrieveVirtualItem(object sender, RetrieveVirtualItemEventArgs e)
		{
			
			PlaylistEntry entry=entries[e.ItemIndex];
			
			//объявляем
			ListViewItem ret;
			string[] subitems=new string[listview.Columns.Count];

			if(entry.TagsReady)
			{
				//тэги уже извлечены или сделана хотя бы попытка
				for(int i=0;i<listview.Columns.Count;i++)
				{
					subitems[i]=(listview.Columns[i].Tag as FieldInfo).GetValue(entry.EntryTags) as string;
					if(subitems[i]==null) subitems[i]=string.Empty;
				}
			}
			else
			{
				//тэги ещё не извлекались, отображаем в ui как имя файла
				//и запускаем поток для извлечения тэгов
				subitems[0]=entry.EntryUri;
				
				for(int i=1;i<listview.Columns.Count;i++)
				{
					subitems[i]=string.Empty;
				}
				
				ThreadPool.QueueUserWorkItem(entry.update_tag_async,null);
			}
			
			ret=new ListViewItem(subitems);
			
			//серым цветом, если стоит статус Entry failed
			if(entry.EntryFailed) ret.ForeColor=System.Drawing.SystemColors.GrayText;
			
			e.Item=ret;
		}

		#endregion

		private List<PlaylistEntry> entries=new List<PlaylistEntry>();

		public PlaylistConfig Config { get; private set; }
		private PlaylistEntry current_entry;
		private Random random_gen;

		/// <summary>
		/// Осторожно, коллбэк в фоновом потоке!
		/// </summary>
		public event EventHandler<PlaylistTagEntryEventArgs> TagsUpdated;

		public PlayListTag()
		{
			Config = new PlaylistConfig();
		}

		/// <summary>
		/// add one file or network url
		/// </summary>
		/// <param name="entry_uri"></param>
		public void AddEntry(string entry_uri)
		{
			PlaylistEntry new_entry=new PlaylistEntry(entry_uri);
			new_entry.TagsUpdated += new EventHandler(EntryTagsUpdated);
			entries.Add(new_entry);
			
			if (listview != null) listview.VirtualListSize = entries.Count;
		}

		/// <summary>
		/// Вызывается потоком из пула когда он заканчивает извлечение тегов
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		void EntryTagsUpdated(object sender, EventArgs e)
		{
			if (listview == null) return;

			if (listview.InvokeRequired)
			{
				listview.BeginInvoke(new EventHandler(EntryTagsUpdated), sender, e);
			}
			else
			{
				PlaylistEntry sender_instance=sender as PlaylistEntry;
				if (sender_instance == null) return;
				int ind=entries.IndexOf(sender_instance);
				listview.Invalidate(listview.Items[ind].Bounds);
				listview.Update();
			}
		}

		public void InsertEntry(string entry_uri, int position)
		{
			PlaylistEntry new_entry=new PlaylistEntry(entry_uri);
			entries.Insert(position, new_entry);
			new_entry.TagsUpdated += new EventHandler(EntryTagsUpdated);
			if (listview != null) listview.VirtualListSize = entries.Count;

			//ThreadPool.QueueUserWorkItem(new WaitCallback(update_entry_tags), new_entry);
		}

		public PlaylistEntry PreviousEntry()
		{
			PlaylistEntry ret=new PlaylistEntry();

			int ret_ind=-1;

			if (Config.ConfRandom)
			{
				//для рандом предыдущего в списке не бывает
				//выкинем-ка исключение
				throw new ApplicationException("Cannot go to previous entry in random mode");
			}
			else
			{
				if (entries.Count == 0) return new PlaylistEntry();

				if ((current_entry == null) || (!entries.Contains(current_entry))) return new PlaylistEntry();

				if (entries.Count == 1)
				{
					return Config.ConfLoop ? current_entry : new PlaylistEntry();
				}

				int i=entries.IndexOf(current_entry);
				int tries_count=0;
				while (true)
				{
					i--;
					tries_count++;
					if (i < 0) //достигли начала
					{
						if (Config.ConfLoop)
						{
							i = entries.Count - 1; //прыгаем в конец
						}
						else
						{
							break;
						}
					}

					if (tries_count > entries.Count) //прошли уже все
					{
						break;
					}

					if (!entries[i].EntryFailed)
					{
						ret_ind = i;
						break;
					}
				}
			}

			if (ret_ind == -1)
			{
				ret = new PlaylistEntry();
			}
			else
			{
				ret = entries[ret_ind];
				ret.PlayOnce = true;
				current_entry = ret;
			}

			

			return ret;
		}

		public PlaylistEntry NextEntry()
		{
			PlaylistEntry ret=new PlaylistEntry();

			int ret_ind=-1;

			if (Config.ConfRandom)
			{
				//соберем индексы элементов для участия в выборе:
				List<int> available_indices=new List<int>();
				for (int i=0; i < entries.Count; i++)
				{
					if ((!entries[i].EntryFailed) && ((!entries[i].PlayOnce) || (Config.ConfLoop)))
					{
						available_indices.Add(i);
					}
				}
				//выбираем из полученных индексов случайный
				if (random_gen == null) random_gen = new Random();
				if (available_indices.Count == 0) ret_ind = -1;
				else if (available_indices.Count == 1) ret_ind = available_indices[0];
				else ret_ind = available_indices[random_gen.Next(0, available_indices.Count - 1)];
			}
			else
			{
				//начинаем просмотр с current_entry
				int cur_ind=(current_entry == null) || (!entries.Contains(current_entry)) ? -1 : entries.IndexOf(current_entry);
				int i=cur_ind + 1;
				int try_count=0;

				

				while (true)
				{
					if (i > entries.Count - 1) i = 0; //зацикливаем к началу списка

					//перебради все элементы, ничего вернуть не можем (или если список пуст)
					if (try_count > entries.Count - 1)
					{
						break;
					}

					if ((!entries[i].EntryFailed) && ((!entries[i].PlayOnce) || (Config.ConfLoop)))
					{
						ret_ind = i;
						break;
					}

					i++;
					
					try_count++;
				}
			}

			if (ret_ind == -1)
			{
				ret = new PlaylistEntry();
			}
			else
			{
				ret = entries[ret_ind];
				ret.PlayOnce = true;
				current_entry = ret;
			}
			return ret;
		}
	}

	class PlaylistEntry
	{
		public PlaylistEntry()
		{
			EntryUri = string.Empty;
			EntryTags = new TAG_INFO();
			TagsReady = false;
			EntryFailed = false;
			PlayOnce = false;
		}
		
		public PlaylistEntry(string entry_uri)
		{
			EntryUri = entry_uri == null ? string.Empty : entry_uri;
			EntryTags = new TAG_INFO();
			TagsReady = false;
			EntryFailed = false;
			PlayOnce = false;
		}

		internal void update_tag_async(object state)
		{
			TAG_INFO tags=BassTags.BASS_TAG_GetFromFile(EntryUri,true,true);
			if (tags==null)
			{
				EntryTags.title=EntryUri;
				EntryFailed=true;
			}
			else
			{
				EntryTags=tags;
			}
			
			TagsReady = true;
			if (TagsUpdated != null) TagsUpdated(this, new EventArgs());
		}

		public string EntryUri { get; set; }
		public TAG_INFO EntryTags { get; set; }

		private object tags_ready_sync=new object();
		private bool tags_ready_internal=false;
		public bool TagsReady
		{
			get
			{
				bool ret=false;
				lock (tags_ready_sync)
				{
					ret = tags_ready_internal;
				}
				return ret;
			}
			set
			{
				lock (tags_ready_sync)
				{
					tags_ready_internal = value;
				}
			}
		}

		private object entry_failed_sync=new object();
		private bool entry_failed_internal=false;
		public bool EntryFailed
		{
			get
			{
				bool ret=false;
				lock (entry_failed_sync)
				{
					ret = entry_failed_internal;
				}
				return ret;
			}
			set
			{
				lock (entry_failed_sync)
				{
					entry_failed_internal = value;
				}
			}
		}

		public bool PlayOnce { get; set; }

		/// <summary>
		/// В фоновом потоке!
		/// </summary>
		public event EventHandler TagsUpdated;
	}

	class PlaylistConfig
	{
		public bool ConfLoop
		{
			get { return Settings.Default.PlaylistLoop; }
			set { Settings.Default.PlaylistLoop = value; }
		}

		public bool ConfRandom
		{
			get { return Settings.Default.PlaylistRandom; }
			set { Settings.Default.PlaylistRandom = value; }
		}
	}

	class PLaylistColumns
	{
		private static readonly string[] available_columns=new string[]
		{"album",
		"albumartist",
		"artist",
		"bitrate",
		"filename",
		"comment",
		"composer",
		"conductor",
		"copyright",
		"disc",
		"duration",
		"encodedby",
		"genre",
		"grouping",
		"isrc",
		"mood",
		"publisher",
		"rating",
		"title",
		"track",
		"year"};
		
		public static string AvailableColumnsH(int index)
		{
			return lang.ResourceManager.GetString("tag_"+available_columns[index],lang.Culture);
		}
		
		public static string AvailableColumns(int index)
		{
			return available_columns[index];
		}
		
		public static int AvailableColumnsCount
		{
			get{return available_columns.Length;}
		}
		
		public static IEnumerable<int> VisibleColumns
		{
			get
			{
				foreach(string s in Properties.Settings.Default.PlaylistVisibleColumns)
				{
					yield return int.Parse(s);
				}
			}
		}
		
		public static void AddVisibleColumn(int index)
		{
			Properties.Settings.Default.PlaylistVisibleColumns.Add(index.ToString());
		}
		
		public static void RemoveAtVisibleColumn(int index)
		{
			Properties.Settings.Default.PlaylistVisibleColumns.RemoveAt(index);
		}
	}
	
	class PlaylistTagEntryEventArgs : EventArgs
	{
		public PlaylistEntry Entry { get; set; }

		public PlaylistTagEntryEventArgs()
		{
			
		}

		public PlaylistTagEntryEventArgs(PlaylistEntry entry)
		{
			Entry = entry;
		}
	}
}
